{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Data Storage Format"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:05.390645Z",
     "iopub.status.busy": "2021-06-24T11:25:05.389017Z",
     "iopub.status.idle": "2021-06-24T11:25:05.974794Z",
     "shell.execute_reply": "2021-06-24T11:25:05.973325Z"
    }
   },
   "outputs": [],
   "source": [
    "import openpnm as op\n",
    "%config InlineBackend.figure_formats = ['svg']\n",
    "import numpy as np\n",
    "np.random.seed(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Spreadsheet Analogy\n",
    "\n",
    "The best analogy for explaining data storage in OpenPNM is the humble spreadsheet.  According to this analogy, each pore (or throat) corresponds to a row and each property corresponds to a column."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Consider the following network with 4 pores, 3 throats:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:05.982588Z",
     "iopub.status.busy": "2021-06-24T11:25:05.981148Z",
     "iopub.status.idle": "2021-06-24T11:25:05.996934Z",
     "shell.execute_reply": "2021-06-24T11:25:05.995763Z"
    }
   },
   "outputs": [],
   "source": [
    "pn = op.network.Cubic(shape=[4, 1, 1])\n",
    "geo = op.geometry.SpheresAndCylinders(network=pn, pores=pn.Ps, throats=pn.Ts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's use ``pandas`` to express the geometric properties as a 'spreadsheet':"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.008204Z",
     "iopub.status.busy": "2021-06-24T11:25:06.006667Z",
     "iopub.status.idle": "2021-06-24T11:25:06.010685Z",
     "shell.execute_reply": "2021-06-24T11:25:06.009551Z"
    }
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "# Let's pull out only the pore properties from the geometry\n",
    "pore_data_sheet = pd.DataFrame({i: geo[i] for i in geo.props(element='pore')})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can now view this 'spreadsheet':"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.032478Z",
     "iopub.status.busy": "2021-06-24T11:25:06.030997Z",
     "iopub.status.idle": "2021-06-24T11:25:06.052123Z",
     "shell.execute_reply": "2021-06-24T11:25:06.053349Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>pore.area</th>\n",
       "      <th>pore.max_size</th>\n",
       "      <th>pore.diameter</th>\n",
       "      <th>pore.seed</th>\n",
       "      <th>pore.volume</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0.176763</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.474407</td>\n",
       "      <td>0.474407</td>\n",
       "      <td>0.055905</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0.244190</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.557595</td>\n",
       "      <td>0.557595</td>\n",
       "      <td>0.090773</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0.197436</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.501382</td>\n",
       "      <td>0.501382</td>\n",
       "      <td>0.065994</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>0.175302</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.472442</td>\n",
       "      <td>0.472442</td>\n",
       "      <td>0.055213</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   pore.area  pore.max_size  pore.diameter  pore.seed  pore.volume\n",
       "0   0.176763            1.0       0.474407   0.474407     0.055905\n",
       "1   0.244190            1.0       0.557595   0.557595     0.090773\n",
       "2   0.197436            1.0       0.501382   0.501382     0.065994\n",
       "3   0.175302            1.0       0.472442   0.472442     0.055213"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [

    "pore_data_sheet"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The properties are the 'column' names, such as 'pore.area', and the rows correspond to the pore index, so 'pore 0' has an area of 0.176763.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One could also extract an entire column using:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.060457Z",
     "iopub.status.busy": "2021-06-24T11:25:06.059315Z",
     "iopub.status.idle": "2021-06-24T11:25:06.063503Z",
     "shell.execute_reply": "2021-06-24T11:25:06.064416Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0    0.176763\n",
      "1    0.244190\n",
      "2    0.197436\n",
      "3    0.175302\n",
      "Name: pore.area, dtype: float64\n"
     ]
    }
   ],
   "source": [
    "pore_area_column = pore_data_sheet['pore.area']\n",
    "print(pore_area_column)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And then access individual elements:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.071219Z",
     "iopub.status.busy": "2021-06-24T11:25:06.070328Z",
     "iopub.status.idle": "2021-06-24T11:25:06.075321Z",
     "shell.execute_reply": "2021-06-24T11:25:06.074525Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.17676309790984798\n"
     ]
    }
   ],
   "source": [
    "print(pore_area_column[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Numpy Arrays Stored in Dictionaries\n",
    "\n",
    "Although the spreadsheet analogy described above is very close to reality, OpenPNM does not actually use ``pandas`` DataFrames, or any other spreadsheet data structure.  Instead, it uses the basic Python *dictionary*, filled with Numpy arrays to accomplish a nearly identical behavior, but with a bit more flexibility.\n",
    "\n",
    "Each OpenPNM object (e.g. networks, algorithms, etc) is actually a customized (a.k.a. [subclassed](https://realpython.com/python-data-classes/#inheritance)) Python [*dictionary*](https://realpython.com/python-dicts/) which allows data to be stored and accessed by name, with a syntax like ``network['pore.diameter']``.  This is analogous (actually indistinguishable) to extracting a column from a spreadsheet as outlined above.  Once the data array is retrieved from the dictionary, it is then a simple matter of working with a Numpy array, which use familiar array indexing, so the diameter of pore 0 is found be accessing element 0 of the array stored under the 'pore.diameter' key.  This is elaborated upon below."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Quick Overview of Dictionaries\n",
    "The internet contains many [tutorials](https://realpython.com/python-dicts/) on Python \"dicts\".  To summarize, they are general purpose data contains where *items* can be stored by name, as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.083478Z",
     "iopub.status.busy": "2021-06-24T11:25:06.082587Z",
     "iopub.status.idle": "2021-06-24T11:25:06.088502Z",
     "shell.execute_reply": "2021-06-24T11:25:06.087478Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'item 1': 'a string', 'item 2': 4, 'another item': {}}\n"
     ]
    }
   ],
   "source": [
    "a = {}  # Create an empty dict\n",
    "a['item 1'] = 'a string'\n",
    "a['item 2'] = 4  # a number\n",
    "a['another item'] = {}  # Even other dicts!\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Data can be accessed by name, which is called a \"key\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.096721Z",
     "iopub.status.busy": "2021-06-24T11:25:06.095485Z",
     "iopub.status.idle": "2021-06-24T11:25:06.100124Z",
     "shell.execute_reply": "2021-06-24T11:25:06.101252Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4\n"
     ]
    }
   ],
   "source": [
    "print(a['item 2'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And all keys on a given dictionary can be retrieved as a list:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.111091Z",
     "iopub.status.busy": "2021-06-24T11:25:06.109614Z",
     "iopub.status.idle": "2021-06-24T11:25:06.114390Z",
     "shell.execute_reply": "2021-06-24T11:25:06.115533Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "dict_keys(['item 1', 'item 2', 'another item'])\n"
     ]
    }
   ],
   "source": [
    "print(a.keys())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> Following the spreadsheet analogy, the dictionary is the sheet and the keys are the column names."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Quick Overview of Numpy Arrays\n",
    "\n",
    "OpenPNM uses *dictionaries* or `dict`'s to store an assortment of [Numpy arrays](https://docs.scipy.org/doc/numpy/user/) that each contain a specific type of pore or throat data.  \n",
    "\n",
    "There are many [tutorials](https://realpython.com/numpy-array-programming/) on the internet explaining the various features and benefits of Numpy arrays.  To summarize, they are familiar to numerical arrays in any other language.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's extract a Numpy array from the ``geo`` object and play with it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.124399Z",
     "iopub.status.busy": "2021-06-24T11:25:06.123519Z",
     "iopub.status.idle": "2021-06-24T11:25:06.128137Z",
     "shell.execute_reply": "2021-06-24T11:25:06.128934Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.47440675 0.55759468 0.50138169 0.47244159]\n"
     ]
    }
   ],
   "source": [
    "a = geo['pore.diameter']\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It's possible to extract several elements at once:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.138325Z",
     "iopub.status.busy": "2021-06-24T11:25:06.137091Z",
     "iopub.status.idle": "2021-06-24T11:25:06.141908Z",
     "shell.execute_reply": "2021-06-24T11:25:06.143209Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.55759468, 0.50138169])"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a[1:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And easy to multiply all values in the array by a scalar or by another array of the same size, which defaults to element-wise multiplication:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.151124Z",
     "iopub.status.busy": "2021-06-24T11:25:06.150235Z",
     "iopub.status.idle": "2021-06-24T11:25:06.155112Z",
     "shell.execute_reply": "2021-06-24T11:25:06.155790Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.9488135  1.11518937 1.00276338 0.94488318]\n"
     ]
    }
   ],
   "source": [
    "print(a*2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.163281Z",
     "iopub.status.busy": "2021-06-24T11:25:06.162384Z",
     "iopub.status.idle": "2021-06-24T11:25:06.167141Z",
     "shell.execute_reply": "2021-06-24T11:25:06.167821Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.22506177 0.31091183 0.2513836  0.22320106]\n"
     ]
    }
   ],
   "source": [
    "print(a*a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> Referring back to the spreadsheet analogy, the Numpy arrays represent the actual cells of the sheet, with the array index corresponding to the row number."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Rules to Maintain Data Integrity\n",
    "\n",
    "Several rules have been implemented to control the integrity of the data:\n",
    "\n",
    "#### All Values are Converted to Numpy Arrays\n",
    "Only Numpy arrays can be stored in an OpenPNM object, and any data that is written into one of the OpenPNM object dicionaries will be converted to a Numpy array.  This is done to ensure that all mathematically operations throughout the code can be consistently done using vectorization.  Note that any subclasses of Numpy arrays, such as Dask arrays or Unyt arrays are also acceptable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.176510Z",
     "iopub.status.busy": "2021-06-24T11:25:06.175396Z",
     "iopub.status.idle": "2021-06-24T11:25:06.180431Z",
     "shell.execute_reply": "2021-06-24T11:25:06.181503Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'numpy.ndarray'>\n"
     ]
    }
   ],
   "source": [
    "pn['throat.list'] = [1, 2, 3]\n",
    "print(type(pn['throat.list']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This illustrates that the basic python list-type has been converted to a Numpy array when stored in the dictionary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Any Scalars are Expanded to a Full-Length Vector\n",
    "For the sake of consistency only arrays of length *Np* or *Nt* are allowed in the dictionary. Assigning a scalar value to a dictionary results in the creation of a full length vector, either *Np* or *Nt* long, depending on the name of the array..  This effectively applies the scalar value to all locations in the network.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.191724Z",
     "iopub.status.busy": "2021-06-24T11:25:06.190288Z",
     "iopub.status.idle": "2021-06-24T11:25:06.195032Z",
     "shell.execute_reply": "2021-06-24T11:25:06.196182Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 0 0 0]\n"
     ]
    }
   ],
   "source": [
    "pn['pore.test'] = 0\n",
    "print(pn['pore.test'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note how the scalar value has been cast to an array of 4 elements long, one for each pore in the network. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Dictionary Keys Must Start With 'pore' or 'throat'\n",
    "All array names must begin with either *'pore.'* or *'throat.'* which serves to identify the type of information they contain."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.204358Z",
     "iopub.status.busy": "2021-06-24T11:25:06.203478Z",
     "iopub.status.idle": "2021-06-24T11:25:06.208127Z",
     "shell.execute_reply": "2021-06-24T11:25:06.208790Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "This will throw an exception since the dict name cannot start with foo\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    pn['foo.bar'] = 0\n",
    "except:\n",
    "    print('This will throw an exception since the dict name cannot start with foo')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Nesting Dictionary Names are Allowed\n",
    "It's possible to create nested properties by assigning a dictionary containing numpy arrays"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.218609Z",
     "iopub.status.busy": "2021-06-24T11:25:06.217483Z",
     "iopub.status.idle": "2021-06-24T11:25:06.222264Z",
     "shell.execute_reply": "2021-06-24T11:25:06.223411Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'pore.concentration.species_A': array([0, 0, 0, 0]), 'pore.concentration.species_B': array([1, 1, 1, 1])}\n"
     ]
    }
   ],
   "source": [
    "pn['pore.concentration'] = {'species_A': 0, 'species_B': 1}\n",
    "print(pn['pore.concentration'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The above rule about expanding the scalar values to a numpy array have been applied.  \n",
    "\n",
    "Requesting the top level of dictionary key returns both concentrations, but they can accessed directly too:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.231472Z",
     "iopub.status.busy": "2021-06-24T11:25:06.230502Z",
     "iopub.status.idle": "2021-06-24T11:25:06.235503Z",
     "shell.execute_reply": "2021-06-24T11:25:06.236221Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 0 0 0]\n"
     ]
    }
   ],
   "source": [
    "print(pn['pore.concentration.species_A'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Unfortunately, the following does not work:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.244270Z",
     "iopub.status.busy": "2021-06-24T11:25:06.243340Z",
     "iopub.status.idle": "2021-06-24T11:25:06.248217Z",
     "shell.execute_reply": "2021-06-24T11:25:06.249026Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The request for pore.concentration returns a dictionary with both arrays, but the entire property name is still used as the key. \n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    pn['pore.concentration']['species_A']\n",
    "except:\n",
    "    print('The request for pore.concentration returns a dictionary with both arrays, '\n",
    "    + 'but the entire property name is still used as the key. ')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Boolean Arrays are Treated as Labels\n",
    "Any Boolean data will be treated as a *label* while all other numerical data is treated as a *property*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.257992Z",
     "iopub.status.busy": "2021-06-24T11:25:06.256769Z",
     "iopub.status.idle": "2021-06-24T11:25:06.261857Z",
     "shell.execute_reply": "2021-06-24T11:25:06.262995Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "1     : pore.all\n",
      "2     : pore.geo_01\n",
      "3     : pore.internal\n",
      "4     : pore.label\n",
      "5     : pore.left\n",
      "6     : pore.right\n",
      "7     : pore.surface\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "pn['pore.label'] = False\n",
    "print(pn.labels(element='pore'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can see that ``'pore.label'`` shows up in this list automatically since it is of Boolean type.  For more information on using labels see [the tutorial on Using and Creating Labels](./using_and_creating_labels.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Dictionary Keys with a Leading Underscore are Hidden\n",
    "Following the Python convention, if a piece of data is not really meant to be seen or used by the user, it can be pre-pended with an underscore and it will no appear in any output."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.271759Z",
     "iopub.status.busy": "2021-06-24T11:25:06.270788Z",
     "iopub.status.idle": "2021-06-24T11:25:06.275494Z",
     "shell.execute_reply": "2021-06-24T11:25:06.276273Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "1     : pore.concentration.species_A\n",
      "2     : pore.concentration.species_B\n",
      "3     : pore.coords\n",
      "4     : pore.test\n",
      "5     : throat.conns\n",
      "6     : throat.list\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "pn['pore._hidden'] = 1\n",
    "print(pn.props())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``'pore._hidden'`` key does not show up in this list."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Representing Topology\n",
    "\n",
    "Consider the following simple random network:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.285814Z",
     "iopub.status.busy": "2021-06-24T11:25:06.284955Z",
     "iopub.status.idle": "2021-06-24T11:25:06.303321Z",
     "shell.execute_reply": "2021-06-24T11:25:06.304283Z"
    }
   },
   "outputs": [],
   "source": [
    "np.random.seed(10)\n",
    "pts = np.random.rand(5, 2)*1.5\n",
    "pn = op.network.Delaunay(points=pts, shape=[1, 1, 0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.310855Z",
     "iopub.status.busy": "2021-06-24T11:25:06.309449Z",
     "iopub.status.idle": "2021-06-24T11:25:06.705586Z",
     "shell.execute_reply": "2021-06-24T11:25:06.706741Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.collections.PathCollection at 0x7f33e79efa00>"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       "  \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<svg height=\"281.490141pt\" version=\"1.1\" viewBox=\"0 0 347.4 281.490141\" width=\"347.4pt\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       " <metadata>\n",
       "  <rdf:RDF xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n",
       "   <cc:Work>\n",
       "    <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\"/>\n",
       "    <dc:date>2021-06-24T07:25:06.679146</dc:date>\n",
       "    <dc:format>image/svg+xml</dc:format>\n",
       "    <dc:creator>\n",
       "     <cc:Agent>\n",
       "      <dc:title>Matplotlib v3.4.2, https://matplotlib.org/</dc:title>\n",
       "     </cc:Agent>\n",
       "    </dc:creator>\n",
       "   </cc:Work>\n",
       "  </rdf:RDF>\n",
       " </metadata>\n",
       " <defs>\n",
       "  <style type=\"text/css\">*{stroke-linecap:butt;stroke-linejoin:round;}</style>\n",
       " </defs>\n",
       " <g id=\"figure_1\">\n",
       "  <g id=\"patch_1\">\n",
       "   <path d=\"M 0 281.490141 \n",
       "L 347.4 281.490141 \n",
       "L 347.4 0 \n",
       "L 0 0 \n",
       "z\n",
       "\" style=\"fill:none;\"/>\n",
       "  </g>\n",
       "  <g id=\"axes_1\">\n",
       "   <g id=\"LineCollection_1\">\n",
       "    <path clip-path=\"url(#paa55c05457)\" d=\"M 312.45 219.758065 \n",
       "L 224.602132 173.466662 \n",
       "\" style=\"fill:none;stroke:#ff0000;stroke-width:2;\"/>\n",
       "    <path clip-path=\"url(#paa55c05457)\" d=\"M 312.45 219.758065 \n",
       "L 34.95 252.032629 \n",
       "\" style=\"fill:none;stroke:#ff0000;stroke-width:2;\"/>\n",
       "    <path clip-path=\"url(#paa55c05457)\" d=\"M 300.814586 32.238031 \n",
       "L 224.602132 173.466662 \n",
       "\" style=\"fill:none;stroke:#ff0000;stroke-width:2;\"/>\n",
       "    <path clip-path=\"url(#paa55c05457)\" d=\"M 224.602132 173.466662 \n",
       "L 119.892117 29.457512 \n",
       "\" style=\"fill:none;stroke:#ff0000;stroke-width:2;\"/>\n",
       "    <path clip-path=\"url(#paa55c05457)\" d=\"M 224.602132 173.466662 \n",
       "L 34.95 252.032629 \n",
       "\" style=\"fill:none;stroke:#ff0000;stroke-width:2;\"/>\n",
       "    <path clip-path=\"url(#paa55c05457)\" d=\"M 119.892117 29.457512 \n",
       "L 34.95 252.032629 \n",
       "\" style=\"fill:none;stroke:#ff0000;stroke-width:2;\"/>\n",
       "   </g>\n",
       "   <g id=\"text_1\">\n",
       "    <g id=\"patch_2\">\n",
       "     <path clip-path=\"url(#paa55c05457)\" d=\"M 261.323068 203.766343 \n",
       "L 268.345522 206.762734 \n",
       "Q 271.656699 208.17557 273.069536 204.864393 \n",
       "L 277.627405 194.182398 \n",
       "Q 279.040241 190.871221 275.729064 189.458385 \n",
       "L 268.70661 186.461994 \n",
       "Q 265.395433 185.049158 263.982596 188.360335 \n",
       "L 259.424727 199.04233 \n",
       "Q 258.011891 202.353507 261.323068 203.766343 \n",
       "z\n",
       "\" style=\"fill:#ffffff;stroke:#ffffff;stroke-linejoin:miter;\"/>\n",
       "    </g>\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <!-- 0 -->\n",
       "     <g transform=\"translate(263.715324 198.159762)rotate(-336.892684)scale(0.12 -0.12)\">\n",
       "      <defs>\n",
       "       <path d=\"M 2034 4250 \n",
       "Q 1547 4250 1301 3770 \n",
       "Q 1056 3291 1056 2328 \n",
       "Q 1056 1369 1301 889 \n",
       "Q 1547 409 2034 409 \n",
       "Q 2525 409 2770 889 \n",
       "Q 3016 1369 3016 2328 \n",
       "Q 3016 3291 2770 3770 \n",
       "Q 2525 4250 2034 4250 \n",
       "z\n",
       "M 2034 4750 \n",
       "Q 2819 4750 3233 4129 \n",
       "Q 3647 3509 3647 2328 \n",
       "Q 3647 1150 3233 529 \n",
       "Q 2819 -91 2034 -91 \n",
       "Q 1250 -91 836 529 \n",
       "Q 422 1150 422 2328 \n",
       "Q 422 3509 836 4129 \n",
       "Q 1250 4750 2034 4750 \n",
       "z\n",
       "\" id=\"DejaVuSans-30\" transform=\"scale(0.015625)\"/>\n",
       "      </defs>\n",
       "      <use xlink:href=\"#DejaVuSans-30\"/>\n",
       "     </g>\n",
       "    </g>\n",
       "   </g>\n",
       "   <g id=\"text_2\">\n",
       "    <g id=\"patch_3\">\n",
       "     <path clip-path=\"url(#paa55c05457)\" d=\"M 170.781311 245.618714 \n",
       "L 178.382677 244.902852 \n",
       "Q 181.966818 244.565314 181.62928 240.981172 \n",
       "L 180.540368 229.418584 \n",
       "Q 180.20283 225.834442 176.618689 226.171981 \n",
       "L 169.017323 226.887843 \n",
       "Q 165.433182 227.225381 165.77072 230.809522 \n",
       "L 166.859632 242.372111 \n",
       "Q 167.19717 245.956252 170.781311 245.618714 \n",
       "z\n",
       "\" style=\"fill:#ffffff;stroke:#ffffff;stroke-linejoin:miter;\"/>\n",
       "    </g>\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <!-- 1 -->\n",
       "     <g transform=\"translate(170.209782 239.549941)rotate(-5.379989)scale(0.12 -0.12)\">\n",
       "      <defs>\n",
       "       <path d=\"M 794 531 \n",
       "L 1825 531 \n",
       "L 1825 4091 \n",
       "L 703 3866 \n",
       "L 703 4441 \n",
       "L 1819 4666 \n",
       "L 2450 4666 \n",
       "L 2450 531 \n",
       "L 3481 531 \n",
       "L 3481 0 \n",
       "L 794 0 \n",
       "L 794 531 \n",
       "z\n",
       "\" id=\"DejaVuSans-31\" transform=\"scale(0.015625)\"/>\n",
       "      </defs>\n",
       "      <use xlink:href=\"#DejaVuSans-31\"/>\n",
       "     </g>\n",
       "    </g>\n",
       "   </g>\n",
       "   <g id=\"text_3\">\n",
       "    <g id=\"patch_4\">\n",
       "     <path clip-path=\"url(#paa55c05457)\" d=\"M 268.419086 111.245809 \n",
       "L 272.653237 104.892448 \n",
       "Q 274.649693 101.896758 271.654002 99.900301 \n",
       "L 261.98978 93.45965 \n",
       "Q 258.994089 91.463194 256.997633 94.458885 \n",
       "L 252.763482 100.812245 \n",
       "Q 250.767025 103.807936 253.762716 105.804392 \n",
       "L 263.426939 112.245043 \n",
       "Q 266.42263 114.241499 268.419086 111.245809 \n",
       "z\n",
       "\" style=\"fill:#ffffff;stroke:#ffffff;stroke-linejoin:miter;\"/>\n",
       "    </g>\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <!-- 2 -->\n",
       "     <g transform=\"translate(263.346695 107.865351)rotate(-56.318816)scale(0.12 -0.12)\">\n",
       "      <defs>\n",
       "       <path d=\"M 1228 531 \n",
       "L 3431 531 \n",
       "L 3431 0 \n",
       "L 469 0 \n",
       "L 469 531 \n",
       "Q 828 903 1448 1529 \n",
       "Q 2069 2156 2228 2338 \n",
       "Q 2531 2678 2651 2914 \n",
       "Q 2772 3150 2772 3378 \n",
       "Q 2772 3750 2511 3984 \n",
       "Q 2250 4219 1831 4219 \n",
       "Q 1534 4219 1204 4116 \n",
       "Q 875 4013 500 3803 \n",
       "L 500 4441 \n",
       "Q 881 4594 1212 4672 \n",
       "Q 1544 4750 1819 4750 \n",
       "Q 2544 4750 2975 4387 \n",
       "Q 3406 4025 3406 3419 \n",
       "Q 3406 3131 3298 2873 \n",
       "Q 3191 2616 2906 2266 \n",
       "Q 2828 2175 2409 1742 \n",
       "Q 1991 1309 1228 531 \n",
       "z\n",
       "\" id=\"DejaVuSans-32\" transform=\"scale(0.015625)\"/>\n",
       "      </defs>\n",
       "      <use xlink:href=\"#DejaVuSans-32\"/>\n",
       "     </g>\n",
       "    </g>\n",
       "   </g>\n",
       "   <g id=\"text_4\">\n",
       "    <g id=\"patch_5\">\n",
       "     <path clip-path=\"url(#paa55c05457)\" d=\"M 162.697388 104.906666 \n",
       "L 167.798532 110.587471 \n",
       "Q 170.203787 113.266043 172.882359 110.860788 \n",
       "L 181.523545 103.101335 \n",
       "Q 184.202117 100.69608 181.796862 98.017508 \n",
       "L 176.695717 92.336703 \n",
       "Q 174.290462 89.658131 171.61189 92.063386 \n",
       "L 162.970705 99.822839 \n",
       "Q 160.292133 102.228094 162.697388 104.906666 \n",
       "z\n",
       "\" style=\"fill:#ffffff;stroke:#ffffff;stroke-linejoin:miter;\"/>\n",
       "    </g>\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <!-- 3 -->\n",
       "     <g transform=\"translate(167.232824 100.834018)rotate(-311.922621)scale(0.12 -0.12)\">\n",
       "      <defs>\n",
       "       <path d=\"M 2597 2516 \n",
       "Q 3050 2419 3304 2112 \n",
       "Q 3559 1806 3559 1356 \n",
       "Q 3559 666 3084 287 \n",
       "Q 2609 -91 1734 -91 \n",
       "Q 1441 -91 1130 -33 \n",
       "Q 819 25 488 141 \n",
       "L 488 750 \n",
       "Q 750 597 1062 519 \n",
       "Q 1375 441 1716 441 \n",
       "Q 2309 441 2620 675 \n",
       "Q 2931 909 2931 1356 \n",
       "Q 2931 1769 2642 2001 \n",
       "Q 2353 2234 1838 2234 \n",
       "L 1294 2234 \n",
       "L 1294 2753 \n",
       "L 1863 2753 \n",
       "Q 2328 2753 2575 2939 \n",
       "Q 2822 3125 2822 3475 \n",
       "Q 2822 3834 2567 4026 \n",
       "Q 2313 4219 1838 4219 \n",
       "Q 1578 4219 1281 4162 \n",
       "Q 984 4106 628 3988 \n",
       "L 628 4550 \n",
       "Q 988 4650 1302 4700 \n",
       "Q 1616 4750 1894 4750 \n",
       "Q 2613 4750 3031 4423 \n",
       "Q 3450 4097 3450 3541 \n",
       "Q 3450 3153 3228 2886 \n",
       "Q 3006 2619 2597 2516 \n",
       "z\n",
       "\" id=\"DejaVuSans-33\" transform=\"scale(0.015625)\"/>\n",
       "      </defs>\n",
       "      <use xlink:href=\"#DejaVuSans-33\"/>\n",
       "     </g>\n",
       "    </g>\n",
       "   </g>\n",
       "   <g id=\"text_5\">\n",
       "    <g id=\"patch_6\">\n",
       "     <path clip-path=\"url(#paa55c05457)\" d=\"M 129.148396 222.882199 \n",
       "L 136.387002 220.454068 \n",
       "Q 139.800097 219.309174 138.655202 215.896079 \n",
       "L 134.961725 204.885293 \n",
       "Q 133.816831 201.472198 130.403736 202.617092 \n",
       "L 123.16513 205.045223 \n",
       "Q 119.752035 206.190117 120.89693 209.603212 \n",
       "L 124.590407 220.613998 \n",
       "Q 125.735302 224.027093 129.148396 222.882199 \n",
       "z\n",
       "\" style=\"fill:#ffffff;stroke:#ffffff;stroke-linejoin:miter;\"/>\n",
       "    </g>\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <!-- 4 -->\n",
       "     <g transform=\"translate(127.209828 217.103047)rotate(-18.543602)scale(0.12 -0.12)\">\n",
       "      <defs>\n",
       "       <path d=\"M 2419 4116 \n",
       "L 825 1625 \n",
       "L 2419 1625 \n",
       "L 2419 4116 \n",
       "z\n",
       "M 2253 4666 \n",
       "L 3047 4666 \n",
       "L 3047 1625 \n",
       "L 3713 1625 \n",
       "L 3713 1100 \n",
       "L 3047 1100 \n",
       "L 3047 0 \n",
       "L 2419 0 \n",
       "L 2419 1100 \n",
       "L 313 1100 \n",
       "L 313 1709 \n",
       "L 2253 4666 \n",
       "z\n",
       "\" id=\"DejaVuSans-34\" transform=\"scale(0.015625)\"/>\n",
       "      </defs>\n",
       "      <use xlink:href=\"#DejaVuSans-34\"/>\n",
       "     </g>\n",
       "    </g>\n",
       "   </g>\n",
       "   <g id=\"text_6\">\n",
       "    <g id=\"patch_7\">\n",
       "     <path clip-path=\"url(#paa55c05457)\" d=\"M 84.302691 148.208698 \n",
       "L 87.557728 141.302328 \n",
       "Q 89.09252 138.045886 85.836078 136.511094 \n",
       "L 75.33066 131.559792 \n",
       "Q 72.074218 130.025 70.539427 133.281442 \n",
       "L 67.284389 140.187813 \n",
       "Q 65.749598 143.444255 69.00604 144.979047 \n",
       "L 79.511457 149.930349 \n",
       "Q 82.767899 151.46514 84.302691 148.208698 \n",
       "z\n",
       "\" style=\"fill:#ffffff;stroke:#ffffff;stroke-linejoin:miter;\"/>\n",
       "    </g>\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <!-- 5 -->\n",
       "     <g transform=\"translate(78.788788 145.609944)rotate(-64.765057)scale(0.12 -0.12)\">\n",
       "      <defs>\n",
       "       <path d=\"M 691 4666 \n",
       "L 3169 4666 \n",
       "L 3169 4134 \n",
       "L 1269 4134 \n",
       "L 1269 2991 \n",
       "Q 1406 3038 1543 3061 \n",
       "Q 1681 3084 1819 3084 \n",
       "Q 2600 3084 3056 2656 \n",
       "Q 3513 2228 3513 1497 \n",
       "Q 3513 744 3044 326 \n",
       "Q 2575 -91 1722 -91 \n",
       "Q 1428 -91 1123 -41 \n",
       "Q 819 9 494 109 \n",
       "L 494 744 \n",
       "Q 775 591 1075 516 \n",
       "Q 1375 441 1709 441 \n",
       "Q 2250 441 2565 725 \n",
       "Q 2881 1009 2881 1497 \n",
       "Q 2881 1984 2565 2268 \n",
       "Q 2250 2553 1709 2553 \n",
       "Q 1456 2553 1204 2497 \n",
       "Q 953 2441 691 2322 \n",
       "L 691 4666 \n",
       "z\n",
       "\" id=\"DejaVuSans-35\" transform=\"scale(0.015625)\"/>\n",
       "      </defs>\n",
       "      <use xlink:href=\"#DejaVuSans-35\"/>\n",
       "     </g>\n",
       "    </g>\n",
       "   </g>\n",
       "   <g id=\"PathCollection_1\">\n",
       "    <defs>\n",
       "     <path d=\"M 0 11.18034 \n",
       "C 2.965061 11.18034 5.80908 10.002309 7.905694 7.905694 \n",
       "C 10.002309 5.80908 11.18034 2.965061 11.18034 0 \n",
       "C 11.18034 -2.965061 10.002309 -5.80908 7.905694 -7.905694 \n",
       "C 5.80908 -10.002309 2.965061 -11.18034 0 -11.18034 \n",
       "C -2.965061 -11.18034 -5.80908 -10.002309 -7.905694 -7.905694 \n",
       "C -10.002309 -5.80908 -11.18034 -2.965061 -11.18034 0 \n",
       "C -11.18034 2.965061 -10.002309 5.80908 -7.905694 7.905694 \n",
       "C -5.80908 10.002309 -2.965061 11.18034 0 11.18034 \n",
       "z\n",
       "\" id=\"mc45d954a08\" style=\"stroke:#0000ff;stroke-width:2;\"/>\n",
       "    </defs>\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <use style=\"fill:#ffffff;stroke:#0000ff;stroke-width:2;\" x=\"312.45\" xlink:href=\"#mc45d954a08\" y=\"219.758065\"/>\n",
       "     <use style=\"fill:#ffffff;stroke:#0000ff;stroke-width:2;\" x=\"300.814586\" xlink:href=\"#mc45d954a08\" y=\"32.238031\"/>\n",
       "     <use style=\"fill:#ffffff;stroke:#0000ff;stroke-width:2;\" x=\"224.602132\" xlink:href=\"#mc45d954a08\" y=\"173.466662\"/>\n",
       "     <use style=\"fill:#ffffff;stroke:#0000ff;stroke-width:2;\" x=\"119.892117\" xlink:href=\"#mc45d954a08\" y=\"29.457512\"/>\n",
       "     <use style=\"fill:#ffffff;stroke:#0000ff;stroke-width:2;\" x=\"34.95\" xlink:href=\"#mc45d954a08\" y=\"252.032629\"/>\n",
       "    </g>\n",
       "   </g>\n",
       "   <g id=\"text_7\">\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <!-- 0 -->\n",
       "     <g transform=\"translate(308.6325 223.069315)scale(0.12 -0.12)\">\n",
       "      <use xlink:href=\"#DejaVuSans-30\"/>\n",
       "     </g>\n",
       "    </g>\n",
       "   </g>\n",
       "   <g id=\"text_8\">\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <!-- 1 -->\n",
       "     <g transform=\"translate(296.997086 35.549281)scale(0.12 -0.12)\">\n",
       "      <use xlink:href=\"#DejaVuSans-31\"/>\n",
       "     </g>\n",
       "    </g>\n",
       "   </g>\n",
       "   <g id=\"text_9\">\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <!-- 2 -->\n",
       "     <g transform=\"translate(220.784632 176.777912)scale(0.12 -0.12)\">\n",
       "      <use xlink:href=\"#DejaVuSans-32\"/>\n",
       "     </g>\n",
       "    </g>\n",
       "   </g>\n",
       "   <g id=\"text_10\">\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <!-- 3 -->\n",
       "     <g transform=\"translate(116.074617 32.768762)scale(0.12 -0.12)\">\n",
       "      <use xlink:href=\"#DejaVuSans-33\"/>\n",
       "     </g>\n",
       "    </g>\n",
       "   </g>\n",
       "   <g id=\"text_11\">\n",
       "    <g clip-path=\"url(#paa55c05457)\">\n",
       "     <!-- 4 -->\n",
       "     <g transform=\"translate(31.1325 255.343879)scale(0.12 -0.12)\">\n",
       "      <use xlink:href=\"#DejaVuSans-34\"/>\n",
       "     </g>\n",
       "    </g>\n",
       "   </g>\n",
       "  </g>\n",
       " </g>\n",
       " <defs>\n",
       "  <clipPath id=\"paa55c05457\">\n",
       "   <rect height=\"267.090141\" width=\"333\" x=\"7.2\" y=\"7.2\"/>\n",
       "  </clipPath>\n",
       " </defs>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "<Figure size 360x288.746 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [

    "op.topotools.plot_tutorial(pn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The basic premise of how OpenPNM stores topology can be stated in 1 sentence:\n",
    "\n",
    "**The pores on either end of a throat are just another property to be stored, along with diameter, length, etc.**\n",
    "\n",
    "In other words, referring to the above diagram, throat No. 5 has pores 3 and 4 on it's ends.  Using the spreadsheet analogy, this implies a new colunm that stores the pair of pores connected by each throat.  OpenPNM calls this property ``'throat.conns'``: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:06.713161Z",
     "iopub.status.busy": "2021-06-24T11:25:06.712287Z",
     "iopub.status.idle": "2021-06-24T11:25:06.715218Z",
     "shell.execute_reply": "2021-06-24T11:25:06.715912Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0 2]\n",
      " [0 4]\n",
      " [1 2]\n",
      " [2 3]\n",
      " [2 4]\n",
      " [3 4]]\n"
     ]
    }
   ],
   "source": [
    "print(pn['throat.conns'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Inspection of this *Nt-by-2* array shows that each row contains the pore indices corresponding to that throat."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sparse Adjacency Matrices\n",
    "\n",
    "The topology storage scheme described above is actually an [adjacency matrix](http://en.wikipedia.org/wiki/Adjacency_matrix), in a [sparse storage format](http://en.wikipedia.org/wiki/Sparse_matrix) known as IJV or COO.  \n",
    "\n",
    "An adjacency matrix is a *Np*-by-*Np* matrix with non-zero values at location (*i*, *j*) indicating that pores *i* and *j* are connected.  \n",
    "\n",
    "An important feature of the adjacency matrix is that it is highly sparse (mostly zeros) and can be stored with a variety of [sparse storage schemes](https://docs.scipy.org/doc/scipy/reference/sparse.html) offered by Scipy.  OpenPNM stores the adjacency matrix in the 'COO' format, which essentially stores the coordinates (I,J) of the nonzero elements in an two-colume wide array.\n",
    "\n",
    "Thus the throat property called ``'throat.conns'`` is an *Nt*-by-2 array that gives the index of the two pores on either end of a given throat.  \n",
    "\n",
    "#### Additional Thoughts on Sparse Storage\n",
    "\n",
    "* In pore networks there is (usually) no difference between traversing from pore *i* to pore *j* or from pore *j* to pore *i*, so a 1 is also found at location (*j*, *i*) and the matrix is symmetrical.\n",
    "\n",
    "* Since the adjacency matrix is symmetric, it is redundant to store the entire matrix when only the upper triangular part is necessary.  The ``'throat.conns'`` array only stores the upper triangular information, and *i* is always less than *j*.\n",
    "\n",
    "* Although this storage scheme is widely known as *IJV*, the ``scipy.sparse`` module calls this the Coordinate or *COO* storage scheme.\n",
    "\n",
    "* Some tasks are best performed on other types of storages scheme, such as *CSR* or *LIL*.  OpenPNM converts between these internally as necessary, but users can generate a desired format using the ``create_adjacency_matrix`` method which accepts the storage type as an argument (i.e. ``'csr'``, ``'lil'``, etc).  For a discussion of sparse storage schemes and the respective merits, see this [Wikipedia article](http://en.wikipedia.org/wiki/Sparse_matrix)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Topology Rules\n",
    "\n",
    "The only topology definitions required by OpenPNM are:\n",
    "\n",
    "1. A throat connects exactly two pores, no more and no less\n",
    "\n",
    "2. Throats are non-directional, meaning that flow in either direction is equal\n",
    "\n",
    "Other general, but non-essential rules are:\n",
    "\n",
    "3. Pores can have an arbitrary number of throats, including zero; however, pores with zero throats lead to singular matrices and other problems so should be avoided.\n",
    "\n",
    "4. Two pores are generally connected by no more than one throat.  It is technically possible in OpenPNM to have multiple throats between a pair of pores, but it is not rigorosly supported so unintended results may arise.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Historical Aside\n",
    "During the development of OpenPNM, it was debated whether existing Python graph theory packages (such as [graph-tool](http://graph-tool.skewed.de/) or [NetworkX](http://networkx.github.io/)) should be used to store the network topology.  It was decided that network property data should be simply stored as [Numpy ND-arrays](http://www.numpy.org/) as discussed above for the primary reason that this format makes the data storage very transparent and familiar since all engineers are used to working with arrays (i.e. vectors), and also very efficiently since this allows code vectorization.  Fortuitously, around the same time as this discussion, Scipy introduced the [compressed sparse graph library](http://docs.scipy.org/doc/scipy/reference/sparse.csgraph.html), which contains numerous graph theory algorithms that take Numpy arrays as arguments.  Therefore, OpenPNM's topology model is implemented using Numpy arrays, leveraging the ``scipy.sparse.csgraph`` library. Most importantly, this approach is perfectly in tune with the 'spreadsheet' model of data storage."
   ]
  }
 ],
 "metadata": {
  "@webio": {
   "lastCommId": null,
   "lastKernelId": null
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": true,
   "title_cell": "",
   "title_sidebar": "Contents",
   "toc_cell": true,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
